using System;
using System.Reflection;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;

namespace DanielSig.Sorcery
{
	public static class XObject
	{
		private static List<System.ComponentModel.BackgroundWorker> _delayedCalls = new List<BackgroundWorker>();
		
		public static bool HasMethod(this object obj, string methodName)
		{
			var type = obj.GetType();
			return type.GetMethod(methodName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) != null;
		}
		public static T GetPropertyValue<T>(this object obj, string propertyName)
		{
			var type = obj.GetType();
			FieldInfo info = type.GetField(propertyName, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.GetField);
			if(info != null) return (T)info.GetValue(obj);
			PropertyInfo prop = type.GetProperty(propertyName, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.GetProperty);
			if(prop != null) return (T)prop.GetValue(obj, null);
			
			throw new FieldAccessException("Could not access '" + propertyName + "' of " + obj.ToString());
		}
		public static object GetPropertyValueDynamic(this object obj, string propertyName)
		{
			var type = obj.GetType();
			FieldInfo info = type.GetField(propertyName, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.GetField);
			if(info != null) return info.GetValue(obj);
			PropertyInfo prop = type.GetProperty(propertyName, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.GetProperty);
			if(prop != null) return prop.GetValue(obj, null);
			
			throw new FieldAccessException("Could not access '" + propertyName + "' of " + obj.ToString());
		}
		public static object SetPropertyValueDynamic(this object obj, string propertyName, object value)
		{
			var type = obj.GetType();
			if(type.IsValueType)
				throw new ArgumentException("SetPropertyValue only handles reference types");
			FieldInfo info = type.GetField(propertyName, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.SetField);
			if(info != null)
			{
				info.SetValue(obj, value);
				return obj;
			}
			PropertyInfo prop = type.GetProperty(propertyName, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.SetProperty);
			if(prop != null)
			{
				prop.SetValue(obj, value, null);
				return obj;
			}
			throw new FieldAccessException("Could not access '" + propertyName + "' of " + obj.ToString());
		}
		public static object SetPropertyValue<T>(this object obj, string propertyName, T value)
		{
			var type = obj.GetType();
			if(type.IsValueType)
				throw new ArgumentException("SetPropertyValue<T> only handles reference types, please use SetPropertyValue<T, U> instead for value types");
			FieldInfo info = type.GetField(propertyName, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.SetField);
			if(info != null)
			{
				info.SetValue(obj, value);
				return obj;
			}
			PropertyInfo prop = type.GetProperty(propertyName, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.SetProperty);
			if(prop != null)
			{
				prop.SetValue(obj, value, null);
				return obj;
			}
			throw new FieldAccessException("Could not access '" + propertyName + "' of " + obj.ToString());
		}
		public static U SetPropertyValue<T, U>(this U obj, string propertyName, T value) where U : struct
		{
			var type = obj.GetType();
			FieldInfo info = type.GetField(propertyName, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.SetField);
			if(info != null)
			{
				System.Object box = (Object)obj;
				info.SetValue(box, value);
				obj = (U)box;
				return obj;
			}
			PropertyInfo prop = type.GetProperty(propertyName, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.SetProperty);
			if(prop != null)
			{
				prop.SetValue(obj, value, null);
				return obj;
			}
			throw new FieldAccessException("Could not access '" + propertyName + "' of " + obj.ToString());
		}
		public static System.Reflection.MethodInfo GetMethod(this object obj, string methodName)
		{
			var type = obj.GetType();
			return type.GetMethod(methodName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
		}
		public static T Call<T>(this object obj, string methodName, params object[] parameters)
		{
			var type = obj.GetType();
			System.Reflection.MethodInfo info = type.GetMethod(methodName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
			if(info != null)  return (T)info.Invoke(obj, parameters);
			return default(T);
		}
		public static object Call(this Type t, string methodName, params object[] parameters)
		{
			System.Reflection.MethodInfo info = t.GetMethod(methodName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);
			if(info != null)
			{
				return info.Invoke(t, parameters);
			}
			return null;
		}
		public static T Call<T>(this Type t, string methodName, params object[] parameters)
		{
			System.Reflection.MethodInfo info = t.GetMethod(methodName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);
			if(info != null)  return (T)info.Invoke(t, parameters);
			return default(T);
		}
		public static object Call(this object obj, string methodName, params object[] parameters)
		{
			return obj.CallWithParams(methodName, parameters);
		}
		public static void CallDelayed(this object obj, string methodName, float delayInSeconds, params object[] parameters)
		{
			obj.CallDelayedWithParams(methodName, delayInSeconds, parameters);
		}
		public static void CallDelayedWithParams(this object obj, string methodName, float delayInSeconds, object[] parameters)
		{
			if(delayInSeconds <= 0) obj.CallWithParams(methodName, parameters);
			else
			{
				BackgroundWorker worker = new BackgroundWorker();
				worker.DoWork += delegate
				{
					System.Threading.Thread.Sleep(TimeSpan.FromSeconds((double)delayInSeconds));
					obj.CallWithParams(methodName, parameters);
					_delayedCalls.Remove(worker);
				};
				_delayedCalls.Add(worker);
				worker.RunWorkerAsync();
			}
		}
		public static object CallWithParams(this object obj, string methodName, object[] parameters)
		{
			var type = obj.GetType();
			System.Reflection.MethodInfo info = type.GetMethod(methodName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
			if(info != null) return info.Invoke(obj, parameters);
			return null;
		}
		public static T CallWithParams<T>(this object obj, string methodName, object[] parameters)
		{
			var type = obj.GetType();
			System.Reflection.MethodInfo info = type.GetMethod(methodName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
			if(info != null) return (T)(object)info.Invoke(obj, parameters);
			return default(T);
		}
		public static T CallWithParams<T, P>(this object obj, string methodName, P[] parameters)
		{
			var type = obj.GetType();
			System.Reflection.MethodInfo info = type.GetMethod(methodName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
			if(info != null) return (T)(object)info.Invoke(obj, parameters.Cast<object>().ToArray());
			return default(T);
		}
	}
}

